package ch.sv7.tool.dbmanager.db;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;

public class ColumnTypeManager {

	public static boolean isStoredAsCDATA(ColumnType type) {
		if (type == null)
			return false;
		return type.getJDBCType() == Types.CHAR 
			|| type.getJDBCType() == Types.VARCHAR 
			|| type.getJDBCType() == Types.LONGVARCHAR;
	}

	public static Object readFromDatabase(ResultSet rs, int index, ColumnType type) throws SQLException {
		if (type == null)
			throw new IllegalArgumentException("ColumnType cannot be null");
		if (rs == null)
			throw new IllegalArgumentException("ResultSet to set cannot be null");
		switch (type.getJDBCType()) {
		case Types.LONGVARCHAR:
		case Types.CHAR: 
		case Types.VARCHAR:
			return rs.getString(index);

		case Types.LONGVARBINARY:
		case Types.VARBINARY:
		case Types.BINARY:
			return rs.getBytes(index);

		case Types.BLOB:
			Blob blob = rs.getBlob(index);
			if (blob == null)
				return null;
			InputStream bis = blob.getBinaryStream();
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			try {
				copyByteStream(bis, bos);
			} catch (IOException e) {
				return null;
			}
			return bos.toByteArray();

		case Types.CLOB:
			Clob clob = rs.getClob(index);
			if (clob == null)
				return null;
			Reader cis = clob.getCharacterStream();
			StringWriter sw = new StringWriter();
			try {
				copyCharacterStream(cis, sw);
			} catch (IOException e) {
				return null;
			}
			return sw.toString();

		case Types.BIT:
			return new Boolean(rs.getBoolean(index));

		case Types.TINYINT:
		case Types.SMALLINT: 
			return new Short(rs.getShort(index));

		case Types.INTEGER: 
			return new Integer(rs.getInt(index));

		case Types.BIGINT:
			return new Long(rs.getLong(index));

		case Types.FLOAT:
		case Types.DOUBLE:
			return new Double(rs.getDouble(index));

		case Types.REAL:
			return new Float(rs.getFloat(index));

		case Types.NUMERIC: 
		case Types.DECIMAL:
			return rs.getBigDecimal(index);

		case Types.DATE:
			return rs.getDate(index);

		case Types.TIME: 
			return rs.getTime(index);

		case Types.TIMESTAMP:
			return rs.getTimestamp(index);

		case Types.NULL:
			return null;
		}
		throw new IllegalArgumentException("Unhandled Column Type " + type.getJDBCType());
	}

	public static void writeToDatabase(PreparedStatement stmt, int index, ColumnType type, Object value)
			throws SQLException {
		if (type == null)
			throw new IllegalArgumentException("ColumnType cannot be null");
		if (stmt == null)
			throw new IllegalArgumentException("Statement to set cannot be null");
		if (value == null) {
			stmt.setNull(index, type.getJDBCType());
			return;
		}
		switch (type.getJDBCType()) {
		case Types.LONGVARCHAR:
		case Types.CHAR:
		case Types.VARCHAR:
			stmt.setString(index, (String) value);
			break;

		case Types.LONGVARBINARY:
		case Types.VARBINARY:
		case Types.BINARY:
			stmt.setBytes(index, (byte[]) value);
			break;

		case Types.BLOB:
			ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) value);
			stmt.setBinaryStream(index, bis, bis.available());
			break;

		case Types.CLOB:
			ByteArrayInputStream cis = new ByteArrayInputStream(((String) value).getBytes());
			stmt.setBinaryStream(index, cis, cis.available());
			break;

		case Types.BIT:
			stmt.setBoolean(index, ((Boolean) value).booleanValue());
			break;

		case Types.TINYINT:
		case Types.SMALLINT:
			stmt.setShort(index, ((Short) value).shortValue());
			break;

		case Types.INTEGER:
			stmt.setInt(index, ((Integer) value).intValue());
			break;

		case Types.BIGINT:
			stmt.setLong(index, ((Long) value).longValue());
			break;

		case Types.FLOAT:
		case Types.DOUBLE:
			stmt.setDouble(index, ((Double) value).doubleValue());
			break;

		case Types.REAL:
			stmt.setFloat(index, ((Float) value).floatValue());
			break;

		case Types.NUMERIC:
		case Types.DECIMAL:
			stmt.setBigDecimal(index, (BigDecimal) value);
			break;

		case Types.DATE:
			stmt.setDate(index, (Date) value);
			break;

		case Types.TIME:
			stmt.setTime(index, (Time) value);
			break;

		case Types.TIMESTAMP:
			stmt.setTimestamp(index, (Timestamp) value);
			break;

		case Types.NULL:
			stmt.setNull(index, 0);
			break;

		default:
			throw new IllegalArgumentException("Unhandled Column Type " + type.getJDBCType());
		}
	}

	/**
	 * Converts the content of a string field into specific type
	 * @param str
	 * @param type
	 * @param format the format of the input (plain, base64) or null if auto
	 * @return
	 */
	public static Object readFromString(SafeString str, ColumnType type) {
		if (type == null)
			throw new IllegalArgumentException("ColumnType cannot be null");
		if (str == null)
			return null;
		switch (type.getJDBCType()) {
		case Types.LONGVARCHAR:
		case Types.CHAR:
		case Types.VARCHAR:
			return str.getOriginalDataAsString();

		case Types.CLOB:
			return str.getOriginalDataAsByteArray();

		case Types.LONGVARBINARY:
		case Types.VARBINARY:
		case Types.BINARY:
			return str.getOriginalDataAsByteArray();

		case Types.BLOB:
			return str.getOriginalDataAsByteArray();

		case Types.BIT:
			return new Boolean("true".equalsIgnoreCase(str.getOriginalDataAsString()));

		case Types.TINYINT:
		case Types.SMALLINT:
			return new Short(str.getOriginalDataAsString());

		case Types.INTEGER:
			return new Integer(str.getOriginalDataAsString());

		case Types.BIGINT:
			return new Long(str.getOriginalDataAsString());

		case Types.FLOAT:
		case Types.DOUBLE:
			return new Double(str.getOriginalDataAsString());

		case Types.REAL:
			return new Float(str.getOriginalDataAsString());

		case Types.NUMERIC:
		case Types.DECIMAL:
			if (str.equals("false")) {
				return new BigDecimal(0);
			} else if (str.equals("true")) {
				return new BigDecimal(1);
			} else {
				return new BigDecimal(str.getOriginalDataAsString());
			}

		case Types.DATE:
			return Date.valueOf(str.getOriginalDataAsString());

		case Types.TIME:
			return Time.valueOf(str.getOriginalDataAsString());

		case Types.TIMESTAMP:
			return Timestamp.valueOf(str.getOriginalDataAsString());

		case Types.NULL:
			return null;
		}
		throw new IllegalArgumentException("Unhandled Column Type " + type.getJDBCType());
	}

	public static SafeString writeToString(ColumnType type, Object value) {
		if (type == null)
			throw new IllegalArgumentException("ColumnType cannot be null");
		if (value == null)
			return null;
		switch (type.getJDBCType()) {
		case Types.LONGVARCHAR:
		case Types.CHAR:
		case Types.VARCHAR:
			return new SafeString().setOriginalData((String)value);

		case Types.CLOB:
			return new SafeString().setOriginalData((byte[])value);

		case Types.LONGVARBINARY:
		case Types.VARBINARY:
		case Types.BINARY:
			return new SafeString().setOriginalData((byte[])value);

		case Types.BLOB:
			return new SafeString().setOriginalData((byte[])value);

		case Types.BIT:
			return new SafeString().setOriginalData(((Boolean) value).booleanValue() ? "true" : "false");

		case Types.TINYINT:
		case Types.SMALLINT:
			return new SafeString().setOriginalData(((Short) value).toString());

		case Types.INTEGER:
			return new SafeString().setOriginalData(((Integer) value).toString());

		case Types.BIGINT:
			return new SafeString().setOriginalData(((Long) value).toString());

		case Types.FLOAT:
		case Types.DOUBLE:
			return new SafeString().setOriginalData(((Double) value).toString());

		case Types.REAL:
			return new SafeString().setOriginalData(((Float) value).toString());

		case Types.NUMERIC:
		case Types.DECIMAL:
			return new SafeString().setOriginalData(((BigDecimal) value).toString());

		case Types.DATE:
			return new SafeString().setOriginalData(((Date) value).toString());

		case Types.TIME:
			return new SafeString().setOriginalData(((Time) value).toString());

		case Types.TIMESTAMP: 
			return new SafeString().setOriginalData(((Timestamp) value).toString());

		case Types.NULL:
			return null;
		}
		throw new IllegalArgumentException("Unhandled Column Type " + type.getJDBCType());
	}

	public static void copyByteStream(InputStream in, OutputStream out) throws IOException {
		int c;
		while ((c = in.read()) != -1)
			out.write(c);
	}

	public static void copyCharacterStream(Reader in, Writer out) throws IOException {
		int c;
		while ((c = in.read()) != -1)
			out.write(c);
	}
}
